package com.tang.common.exception;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.tang.common.event.LogErrorEvent;
import com.tang.common.utils.CommonUtils;
import com.tang.common.resources.Result;
import com.tang.module.system.entity.LogError;
import com.tang.module.system.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.util.Date;

/**
 * 全局异常处理
 * @author tang
 * @date 2022/1/14 14:32
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandling {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Value("${spring.profiles.active}")
    private String profiles;

    @Value("${spring.application.name}")
    private String name;

    @Value("${server.port}")
    private String port;


    /**
     * 自定义全局异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(GlobalException.class)
    private Result<String> globalException(GlobalException e,HttpServletRequest request){
        log.error("业务异常",e);
        logging(e,request);
        return buildMessage(e,400,e.getMessage());
    }

    /**
     * 自定义安全异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(SecurityException.class)
    private Result<String> securityException(SecurityException e,HttpServletRequest request){
        log.error("安全异常",e);
        logging(e,request);
        return buildMessage(e,403,e.getMessage());
    }

    /**
     * 安全异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(AccessDeniedException.class)
    private Result<String> accessDeniedException(AccessDeniedException e,HttpServletRequest request){
        log.error("security框架访问异常",e);
        logging(e,request);
        return buildMessage(e,403,e.getMessage());
    }

    /**
     * 参数异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    private Result<String> illegalArgumentException(IllegalArgumentException e,HttpServletRequest request){
        log.error("非法参数异常",e);
        logging(e,request);
        return buildMessage(e,400,"非法参数");
    }

    /**
     * 绑定异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    private Result<String> bindException(BindException e,HttpServletRequest request){
        log.error("参数绑定异常",e);
        logging(e,request);
        return buildMessage(e,400,"参数绑定错误");
    }

    /**
     * 绑定异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    private Result<String> missingServletRequestParameterException(MissingServletRequestParameterException e,HttpServletRequest request){
        log.error("缺少必要的请求参数",e);
        logging(e,request);
        return buildMessage(e,400,"缺少必要的请求参数，" + e.getMessage());
    }

    /**
     * sql执行异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(SQLException.class)
    private Result<String> sqlException(SQLException e,HttpServletRequest request){
        log.error("sql异常",e);
        logging(e,request);
        return buildMessage(e,400,"SQL错误");
    }

    /**
     * 通用过滤器执行异常
     * @param e 异常
     * @return 错误消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(GeneralFilterHandlingException.class)
    private Result<String> generalFilterHandlingException(GeneralFilterHandlingException e,HttpServletRequest request){
        log.error("通用过滤器执行异常",e);
        logging(e,request);
        return buildMessage(e,400,e.getMessage());
    }

    /**
     * 请求方式错误异常
     * @param e 异常
     * @return 错误消息
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    private Result<String> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e,HttpServletRequest request){
        log.error("请求方式错误",e);
        logging(e,request);
        return buildMessage(e,500,"请求方式错误");
    }

    /**
     * redis错误异常
     * @param e 异常
     * @return 错误消息
     */
    @ExceptionHandler({RedisSystemException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    private Result<String> redisSystemException(RedisSystemException e, HttpServletRequest request){
        log.error("缓存异常",e);
        logging(e,request);
        return buildMessage(e,500,"缓存异常");
    }

    /**
     * 服务器异常
     * @param e 异常
     * @return 错误消息
     */
    @ExceptionHandler({Throwable.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    private Result<String> throwable(Throwable e,HttpServletRequest request){
        log.error("服务器异常",e);
        logging(e,request);
        return buildMessage(e,500,"服务器内部错误");
    }


    public void logging(Throwable e, HttpServletRequest request){
        LogError logError = new LogError();
        //请求时间
        logError.setCreateTime(new Date());
        //得到异常的第一行
        StackTraceElement stackTraceElement = e.getStackTrace()[0];
        //类
        logError.setMethodClass(stackTraceElement.getClassName());
        //方法
        logError.setMethodName( stackTraceElement.getMethodName());
        //异常消息
        logError.setMessage(e.getMessage());
        //异常发生的行号
        logError.setLineNumber(stackTraceElement.getLineNumber());
        //堆栈
        //logError.setStackTrace(JSON.toJSONString(e));
        //异常名称
        logError.setExceptionName(e.getClass().getName());
        //服务器环境
        logError.setEnv(profiles);
        //请求url
        logError.setRequestUri(request.getRequestURI());
        //请求方式
        logError.setMethod(request.getMethod());
        //请求者IP
        logError.setRemoteIp(CommonUtils.getIpAddr(request));
        //获取本服务器的端口和名字
        logError.setServerIp(port);
        logError.setServerHost(name);
        //请求参数
        logError.setParams(JSON.toJSONString(request.getParameterMap()));
        //获取当前登录用户
        SecurityContext context = SecurityContextHolder.getContext();
        if (context != null && context.getAuthentication() != null){
            User user = (User) context.getAuthentication().getPrincipal();
            logError.setCreateBy(user.getUsername());
        }
        //获取用户代理
        UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
        logError.setBrowser(ua.getBrowser().toString());
        logError.setSystem(ua.getPlatform().toString());
        logError.setMobileTerminal(ua.isMobile());
        applicationEventPublisher.publishEvent(new LogErrorEvent(logError));
    }


    /**
     * 根据当前环境来判断是否返回详细错误信息
     * @param e 异常
     * @param code 应该返回的状态码
     * @param msg 简介的提示
     * @return 结果
     */
    public  Result<String>  buildMessage(Throwable e,int code,String msg){
        return Result.fail(code, "dev".equals(profiles) ? e.getMessage() : msg);
    }

}
